#version 330 core

struct DirLight
{
	vec3 direction;
	vec3 color;
	float biasMax;
	float biasMin;
};

struct PointLight
{
	vec3 position;

	vec3 color;

	float constant;
	float linear;
	float quadratic;
};

struct SpotLight
{
	vec3 position;
	vec3 direction;

	vec3 color;

	float innerCutoff;
	float outerCutoff;

	bool turnOn;
};

struct Material
{
	float ambientStrength;
	float diffuseStrength;
	float specularStrength;
	float shininess;
};

in vec2 texPos;
in vec3 normal;
in vec3 fragPos;
in vec4 fragPosLightSpace;
flat in int isDraw;
flat in uint drawPart;
out vec4 FragColor;

uniform DirLight dirLight;
uniform PointLight pointLight;
uniform SpotLight spotLight;

uniform Material material;

uniform bool donotCalculateLights;

uniform sampler2D texture0;
uniform sampler2D texture1;
uniform sampler2D texture2;
uniform sampler2D texture3;
uniform sampler2D shadowMap;
uniform vec3 viewPos;
uniform bool DisplayDepth;
uniform bool DisplayPosition;
uniform bool DisplayShadow;
uniform bool hasBodyPart;

float ShadowCalculation(vec4 fragPosLightSpace)
{
    // 执行透视除法
    vec3 projCoords = fragPosLightSpace.xyz / fragPosLightSpace.w;
    // 变换到[0,1]的范围
    projCoords = projCoords * 0.5 + 0.5;
    // 取得当前片段在光源视角下的深度
    float currentDepth = projCoords.z;

	// // 取得最近点的深度(使用[0,1]范围下的fragPosLight当坐标)
    // float closestDepth = texture(shadowMap, projCoords.xy).r; 
    // // 检查当前片段是否在阴影中
	// // float bias = 0.005;
	// float bias = max(dirLight.biasMax * (1.0 - dot(normal, -dirLight.direction)), dirLight.biasMin);
	// float shadow = currentDepth - bias > closestDepth  ? 1.0 : 0.0;

	float shadow = 0.0;
	if(projCoords.z <= 1.0)
	{
		float bias = max(dirLight.biasMax * (1.0 - dot(normal, dirLight.direction)), dirLight.biasMin);
		vec2 texelSize = 1.0 / textureSize(shadowMap, 0);
		for(int x = -1; x <= 1; ++x)
		{
			for(int y = -1; y <= 1; ++y)
			{
				float pcfDepth = texture(shadowMap, projCoords.xy + vec2(x, y) * texelSize).r; 
				shadow += currentDepth - bias > pcfDepth ? 1.0 : 0.0;        
			}    
		}
		shadow /= 9.0;
	}

    return shadow;
}

vec3 DoDirLight(vec3 baseColor, DirLight light, vec3 normal, vec3 viewDir)
{
	vec3 lightDir = -light.direction;

	vec3 ambient = baseColor * light.color * material.ambientStrength;

	float diff = max(dot(normal, lightDir), 0.0f);
	vec3 diffuse = diff * baseColor * light.color * material.diffuseStrength;

	vec3 reflectDir = reflect(-lightDir, normal);
	float spec = pow(max(dot(viewDir, reflectDir), 0.0f), material.shininess);
	vec3 specular = spec * baseColor * light.color * material.specularStrength;

	if (DisplayShadow){
		float shadow = 1.0f - ShadowCalculation(fragPosLightSpace);
		return ambient + (diffuse + specular)*shadow;
	}
	else{
		return ambient + (diffuse + specular);
	}
}

vec3 DoPointLight(vec3 baseColor, PointLight light, vec3 normal, vec3 fragPos, vec3 viewDir)
{
	vec3 lightDir = normalize(light.position - fragPos);
	float distance = length(light.position - fragPos);
	float attenuation = 1.0f / (light.constant + light.linear * distance + light.quadratic * distance * distance);

	vec3 ambient = baseColor * light.color * material.ambientStrength;

	float diff = max(dot(lightDir, normal), 0.0f);
	vec3 diffuse = diff * baseColor * light.color * material.diffuseStrength;

	vec3 reflectDir = reflect(-lightDir, normal);
	float spec = pow(max(dot(reflectDir, viewDir), 0.0f), material.shininess);
	vec3 specular = spec * baseColor * light.color * material.specularStrength;

	return (ambient + diffuse + specular) * attenuation;
}

vec3 DoSpotLight(vec3 baseColor, SpotLight light, vec3 normal, vec3 fragPos, vec3 viewDir)
{
	vec3 lightDir = normalize(light.position - fragPos);
	float theta = dot(lightDir, -light.direction);

	vec3 ambient = baseColor * light.color * material.ambientStrength;

	if (theta > light.outerCutoff)
	{
		float theta     = dot(lightDir, -light.direction);
		float epsilon   = light.innerCutoff - light.outerCutoff;
		float intensity = clamp((theta - light.outerCutoff) / epsilon, 0.0, 1.0);

		float diff = max(dot(lightDir, normal), 0.0f);
		vec3 diffuse = diff * baseColor * light.color * material.diffuseStrength;

		vec3 reflectDir = reflect(-lightDir, normal);
		float spec = pow(max(dot(reflectDir, viewDir), 0.0f), material.shininess);
		vec3 specular = spec * baseColor * light.color * material.specularStrength;

		return (diffuse + specular) * intensity + ambient;
	}

	return ambient;
}

void main()
{
	if (isDraw == 0)
		discard;

	bool flag = false;
	if (hasBodyPart && ((drawPart & uint(0x1FF)) != uint(0))){
		flag = true;
	}

	
	// if (gl_FragCoord.z >0.8)
	// 	discard;
	if (donotCalculateLights)
	{
		FragColor = vec4(pointLight.color, 1.0f);
	}
	else
	{
		if (DisplayDepth){
			FragColor = vec4(gl_FragCoord.z, gl_FragCoord.z, gl_FragCoord.z, 1.0f);
		}
		else if (DisplayPosition){
			FragColor = vec4(vec3(fragPos), 1.0f);
		}
		else{
			float alpha =flag ? 1.0f : texture(texture0, texPos).a;
			if (alpha < 0.5) 
				discard;

			vec3 baseColor = texture(texture0, texPos).rgb;

			if (hasBodyPart){
				if (flag)
					FragColor = vec4(1,0,0,1);
				else
					FragColor = vec4(0,1,0,1);
			}
			else{
				vec3 viewDir = normalize(viewPos - fragPos);

				vec3 dirLightColor = DoDirLight(baseColor, dirLight, normal, viewDir);
				vec3 pointLightColor = DoPointLight(baseColor, pointLight, normal, fragPos, viewDir);
				vec3 spotLightColor = vec3(0.0f,0.0f,0.0f);
				if (spotLight.turnOn)
					spotLightColor = DoSpotLight(baseColor, spotLight, normal, fragPos, viewDir);

				FragColor = vec4(dirLightColor + pointLightColor + spotLightColor, alpha);
			}

		}
	}
}

